#include "stdafx.h"
#include "Shader.h"
#include "Texture.h"
#include <Utilities\Extensions.h>
#include <Utilities\Exceptions.h>
#include <FovDrivenRadiosity\Resource.h>

using namespace std;

void ShaderResource::Create(UINT_PTR shaderResourceID, string name)
{
	HINSTANCE hInst = GetModuleHandle(NULL);
	HRSRC hmbdFile = FindResourceA(hInst, LPCSTR(shaderResourceID), "SHADER");
	if(hmbdFile == 0)
		throw new invalid_argument("Wrong resource Id specified.");
	HGLOBAL hResource = LoadResource(hInst, hmbdFile);
	DWORD dwResSize = SizeofResource(hInst, hmbdFile);

	if(hResource)
	{
		_source.append((const char*)LockResource(hResource), dwResSize);
		FreeResource(hResource);
		_valid = true;
	}
	else
	{
		_source = "/* Shader Resource not found */ void main() {} ";
		_valid = false;
	}

	_name = name;
}

const std::string ShaderResource::GetSource() const
{
	return _source;
}

const bool ShaderResource::GetIsValid(void) const
{
	return _valid;
}

const int ShaderResource::GetType(void) const
{
	return _type;
}

VertexShaderResource::VertexShaderResource(UINT_PTR shaderResourceID, string name)
{
	ShaderResource::Create(shaderResourceID, name);
	_type = GL_VERTEX_SHADER;
}

const string VertexShaderResource::GetName(void) const
{
	return "Vertex Shader: " + _name;
}

FragmentShaderResource::FragmentShaderResource(UINT_PTR shaderResourceID, string name)
{
	ShaderResource::Create(shaderResourceID, name);
	_type = GL_FRAGMENT_SHADER;
}

const string FragmentShaderResource::GetName(void) const
{
	return "Fragment Shader: " + _name;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Shader::Shader(void)
{
	_programObject = 0;
	_shaderObjects.clear();
}

Shader::Shader(const vector<ShaderResource*>& shaders)
{
	_programObject = 0;
	_shaderObjects.clear();
	Create(shaders);
}

Shader::~Shader(void)
{
	for(vector<GLuint>::iterator iter = _shaderObjects.begin(); iter != _shaderObjects.end(); iter++)
	{
		glDetachShader(_programObject, *iter); 
		glDeleteShader(*iter);
	}

	glDeleteProgram(_programObject);
}

void Shader::Enable(void) const
{
	Enable(true);
}

void Shader::Enable(bool enable) const
{
	glUseProgram((enable) ? _programObject : NULL);
}

bool Shader::IsEnabled(void) const
{
	GLint currentProgram;
	glGetIntegerv(GL_CURRENT_PROGRAM, &currentProgram);
	return currentProgram == static_cast<GLint>(_programObject);
}

void Shader::Create(const vector<ShaderResource*>& shaders)
{
	string log;
	if(!BuildFiles(shaders, log))
	{
		throw ShaderProgramCompilationException(log);
	}

	InitializeShaderParameters();
}

const bool Shader::BuildFiles(const vector<ShaderResource*>& shaders, string& log)
{
	return Compile(shaders, log) && Link(log);
}

const bool Shader::Compile(const vector<ShaderResource*>& shaders, string& log)
{
	bool success = true;

	for(vector<ShaderResource*>::const_iterator iter = shaders.begin(); iter != shaders.end(); iter++)
	{
		if((*iter)->GetIsValid())
		{
			log	+= "\n" + (*iter)->GetName() + " compile:\n";
			success = success && CompileShader(*iter, log);
		}
	}
	return success;
}

const bool Shader::Link(string& log)
{
	_programObject = glCreateProgram();
	for (vector<GLuint>::iterator iter = _shaderObjects.begin(); iter != _shaderObjects.end(); iter++)
	{
		glAttachShader(_programObject, *iter);
	}

	glLinkProgram(_programObject);
	glValidateProgram(_programObject);
	GLint bLinkSuccess, bValidateSuccess;
	glGetProgramiv(_programObject, GL_LINK_STATUS, &bLinkSuccess);
	glGetProgramiv(_programObject, GL_VALIDATE_STATUS, &bValidateSuccess);
	log += "\nProgram link:\n" + GetProgramInfoLog(_programObject);

	return (bLinkSuccess && bValidateSuccess != 0);
}

const bool Shader::CompileShader(const ShaderResource* const shader, string& log)
{
	const std::string string = shader->GetSource();
	const GLchar* source = string.c_str();

	GLuint shaderObject = glCreateShader(shader->GetType());
	glShaderSource(shaderObject, 1, &source, NULL);
	glCompileShader(shaderObject);

	GLint	success;
	glGetShaderiv(shaderObject, GL_COMPILE_STATUS, &success);
	log += GetShaderInfoLog(shaderObject);

	if(success)
	{
		_shaderObjects.push_back(shaderObject);
	}

	return success != 0;
}

const string Shader::GetShaderInfoLog(const GLuint shader) const
{
	GLint logLength = 0;
	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);

	if (logLength <= 1)
	{
		return string("Successfully compiled.\n");
	}

	string log("", logLength);
	GLsizei charCount = 0;
	glGetShaderInfoLog(shader, logLength, &charCount, &log[0]);

	return log;
}

const string Shader::GetProgramInfoLog(const GLuint program) const
{
	GLint logLength = 0;
	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);

	if (logLength <= 1)
	{
		return string("Successfully linked.\n");
	}

	string log("", logLength);
	GLsizei charCount = 0;
	glGetProgramInfoLog(program, logLength, &charCount, &log[0]);

	return log;
}

const GLuint Shader::GetProgramId() const
{ 
	return _programObject;
}

void Shader::InitializeShaderParameters()
{
	GLint shaderParameters;
	string name("", 256);

	glGetProgramiv(_programObject, GL_ACTIVE_UNIFORMS, &shaderParameters);
	if(shaderParameters > 0)
	{
		for(int i = 0; i < shaderParameters; i++)
		{
			GLint           size, location;
			GLenum          type;

			glGetActiveUniform(_programObject, static_cast<GLuint>(i), static_cast<GLsizei>(name.size()), NULL, &size, &type, &name[0]);
			location = glGetUniformLocation(_programObject, &name[0]);
			if(location != -1)
			{
				_uniformParameters.insert(ShaderMap::value_type(&name[0], location));
			}
		}
	}

	glGetProgramiv(_programObject, GL_ACTIVE_ATTRIBUTES, &shaderParameters);
	if(shaderParameters > 0)
	{	
		for(int i = 0; i < shaderParameters; i++)
		{
			GLint           size, location;
			GLenum          type;

			glGetActiveAttrib(_programObject, (GLuint)i, 256, NULL, &size, &type, &name[0]);
			location = glGetAttribLocation(_programObject, &name[0]);
			if(location != -1)
			{
				_attributeParameters.insert(ShaderMap::value_type(&name[0], location));
			}
		}
	}
}

const int Shader::GetUniformParameter(const std::string name) const
{
	return GetParameter(_uniformParameters, name);
}

const int Shader::GetAttribParameter(const std::string name) const
{
	return GetParameter(_attributeParameters, name);
}

const int Shader::GetParameter(const ShaderMap& map, const std::string name) const
{
	ShaderMap::const_iterator iter = map.find(name);
	if(iter != map.end())
	{
		return iter->second;
	}

	ostringstream str;
	str << "Attempt to access non-existent attribute parameter \"" << name << "\".";
	throw ShaderProgramException(str.str());
}

void Shader::SetUniform1i(const std::string name, const GLint value) const
{
	glUniform1i(GetUniformParameter(name), value);
}

void Shader::SetUniform1f(const std::string name, const GLfloat value) const
{
	glUniform1f(GetUniformParameter(name), value);
}

void Shader::SetUniform2fv(const std::string name, const GLfloat* const value) const
{
	glUniform2fv(GetUniformParameter(name), 1, value);
}

void Shader::SetUniform3fv(const std::string name, const GLfloat* const value) const
{
	glUniform3fv(GetUniformParameter(name), 1, value);
}
